#ifndef _CAL_LEXER_HPP
#define _CAL_LEXER_HPP 1
// expression calculator
// usual_function : sin cos tan max min
#include"lexer_basic.hpp"
#include<iostream>
#include<atomic>
#include<set>

using std::set;

namespace LocalData{
    // --- type sector --- //

    // --- data sector --- //
    static std::set<char> SIGNAL_SET{'=','-','+','*','/','%','^',';'};

    // --- class sector --- //

    // --- function sector --- //

}


class getToken{
    protected:
        /**
         * @brief a copy of the rest at the first
         * then it may change to the Token String
        */
        std::string content;
        size_t pos; // keep the position of the raw content at the start point
        bool run;
        set<char>endChar; // remains to finish
        virtual size_t basicGetTokenStr(std::string const&,size_t)=0;
    public:
        getToken(std::string const&str,size_t p):content(str.substr(pos)),pos(p),run(false){}
        virtual ~getToken(){}
        virtual size_t getPosition(){
            if(!run){
                size_t end=basicGetTokenStr(content,pos);
                content=content.substr(pos,end-pos);
                run=true;
            }return pos+content.size();
        };
        virtual std::string getTokenStr(){
            if(!run){
                size_t end=basicGetTokenStr(content,pos);
                content=content.substr(pos,end-pos);
                run=true;
            }return content;
        };
        // virtual static std::string getTokenStr(std::string const&,size_t &)=0;
        virtual bool isDelim(char)=0;
        static Token getCharType(char c){
            if(LocalData::SIGNAL_SET.count(c)){
                return oper;
            }else if(std::isdigit(c)){
                return number;
            }else if(std::isalpha(c)||c=='_'){
                return identifier;
            }else{
                throw std::runtime_error("unexpected character");
            }
        }
        /**
         * @brief switch context to get diff lexemes
        */
        virtual void Switch(const std::string&str,size_t idx=0){
            content=str;
            pos=idx;
            run=false;
        }
};
// [0-9]+(\.[0-9]+)?
class getTokenNum:public getToken{
    private:
        bool dot;
    protected:
        virtual size_t basicGetTokenStr(std::string const&str,size_t p){
            return staticBasicToken(str,p);
        }
        size_t staticBasicToken(std::string const&,size_t);
    public:
        getTokenNum(std::string const&str,size_t p,bool decimal=true):getToken(str,p),dot(decimal){}
        virtual bool isDelim(char c){
            return ! (std::isdigit(c)||(dot&&c=='.'));
        }
        static std::string getTokenStrStatic(std::string const&str,size_t &pos){
            static getTokenNum gtn(str,pos);
            gtn.Switch(str,pos);
            pos=gtn.getPosition();
            return gtn.getTokenStr();
        }
};
// _[_A-Za-z0-9]*
class getTokenIdent:public getToken{
    private:
    protected:
        virtual size_t basicGetTokenStr(std::string const&str,size_t p){
            return staticBasicToken(str,p);
        }
        size_t staticBasicToken(std::string const&,size_t);
    public:
        getTokenIdent(std::string const&str,size_t p):getToken(str,p){}
        virtual bool isDelim(char);
        static std::string getTokenStrStatic(std::string const&str,size_t &p){
            static getTokenIdent gti(str,p);
            gti.Switch(str,p);
            p=gti.getPosition();
            return gti.getTokenStr();
        }
};


class basic_lexer{
    protected:
        std::vector<std::pair<std::string,Token>>lexemes;
        int parenthesis;
    public:
        basic_lexer():parenthesis(0){}
        virtual ~basic_lexer(){}
        virtual std::vector<std::pair<std::string,Token>>const&
            getLexemes()const noexcept{return lexemes;}
};

class expr_lexer final:public basic_lexer{
    std::string content;
    void progress(bool interrupt=false);
    public:
        expr_lexer(std::string const&str):content(str){}
        // expr_lexer():parenthesis(0){}
        /**
         * @brief do the lexer job
         * @param terminate terminate directly or throw int remaining for capture
         * @warning except may occur
        */
        const std::vector<std::pair<std::string,Token>>& execute(bool terminate=false);
        void show(std::ostream&ofs=std::cout)noexcept{
            for(auto&&it:lexemes){
                try{
                    ofs<<"< "<<it.first<<" , "<<LocalData::DataSingleton::entity().Tk2str.at(it.second)<<" >"<<std::endl;
                }catch(...){/* imposible to access here */}
            }
        }
};
const std::vector<std::pair<std::string,Token>>& expr_lexer::execute(bool terminate){
    static bool pass=false;
    if(!pass){
        progress(terminate);
        pass=true;
    }return this->lexemes;
}
void expr_lexer::progress(bool interrupt){
    for(size_t i=0;i<content.size();){
        if(std::isspace(content[i])){
            ++i;continue;
        }else{
            size_t end;Token tk=oper;
            try{
                switch(tk=getToken::getCharType(content[i])){
                    case oper:
                        // end=i+1;
                        // lexemes[std::string({content[i++]})]=oper;
                        lexemes.push_back({std::string({content[i++]}),oper});
                        break;
                    case number:
                        // lexemes[getTokenNum::getTokenStrStatic(content,i)]=number;
                        lexemes.push_back({getTokenNum::getTokenStrStatic(content,i),number});
                        // lexemes[getTokenNum(content,i).getTokenStr()]=number;
                        break;
                    case identifier:
                        std::string const&temp=getTokenIdent::getTokenStrStatic(content,i);
                        try{
                            LocalData::DataSingleton::entity().str2fn.at(temp);
                            // lexemes[temp]=func;
                            lexemes.push_back({temp,func});
                        }catch(...){
                            // lexemes[temp]=identifier;
                            lexemes.push_back({temp,identifier});
                        }
                }// lexemes[content.substr(i,end-i)]=tk;
            }catch(std::runtime_error const&re){
                // std::cerr<<"Col:"<<i<<":\033[22;31;40m error:\033[0m"<<re.what()
                //         <<" \033[22;31;40m"<<content[i]<<"\033[0m"<<std::endl;
                std::cerr<<"Col:"<<i<<":\033[22;31;40m error:\033[0m"<<re.what()<<std::endl;
                std::cout<<"\t"<<content.substr(0,i)
                        <<" \033[22;31;40m"<<content[i]<<"\033[0m"
                        <<content.substr(i+1)<<std::endl;
                if(interrupt)std::terminate();
                else throw 0;
            }
        }
    }
}

// --- implementation sector --- //
// getTokenNum
size_t getTokenNum::staticBasicToken(std::string const&str,size_t p){
    if(dot){
        unsigned int count=0;
        for(size_t ptr=p;ptr<str.size();++ptr){
            if(str[ptr]=='.'){
                if(count==0&&(ptr!=str.size()-1&&std::isdigit(str[ptr+1])))++count;
                else throw std::runtime_error("unexpected dot");
            }else if(std::isdigit(str[ptr])){ // pass
            }else{ // delim
                return ptr;
            }
        }
    }else{
        while(std::isdigit(str[p])||(dot&&str[p]=='.'))++p;
        return p;
    }
    return str.size();
}
// static std::string getTokenNum::getTokenStrStatic(std::string const&str,size_t &p){
//     size_t endOfToken=staticBasicToken(str,p);
//     const std::string&temp=str.substr(p,endOfToken-p);
//     p=endOfToken;
//     return temp;
// }
// getTokenIdent
size_t getTokenIdent::staticBasicToken(std::string const&str,size_t p){
    while(std::isalnum(str[p])||str[p]=='_')++p;
    return p;
}
// static std::string getTokenIdent::getTokenStrStatic(std::string const&str,size_t &p){
//     size_t endOfToken=staticBasicToken(str,p);
//     const std::string&temp=str.substr(p,endOfToken-p);
//     p=endOfToken;
//     return temp;
// }
bool getTokenIdent::isDelim(char c){
    return !(std::isalnum(c)||c=='_');
}
#endif//_CAL_LEXER_HPP